﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq;
using TrinityAPI.Base.Tools;
using TrinityAPI.Base.Models;

namespace TrinityAPI.Base
{
    public class Authentication : IAction
    {
        public JToken Invoke(JObject dataset, params JToken[] args)
        {
            var constant = dataset.GetJsonObject("constant");

            var authentication = constant.GetJsonObject("authentication");
            var type = authentication.GetJsonObjectStringValue("type", "base");

            try
            {
                switch (type?.ToLower())
                {
                    case "base":
                        Base(dataset);
                        break;
                    case "ip":
                        IP(dataset);
                        break;
                    case "host":
                        Host(dataset);
                        break;
                    case "local":
                        Local(dataset);
                        break;
                    default:
                        throw new BaseException(ErrorCode.E000000029.ToString(), "The value({0}) of [authentication.type] is invalid", type);

                }
            }
            catch (BaseException ex)
            {
                throw ex;
            }
            catch (Exception ex)
            {
                LogHelper.Info("Authentication fail: " + ex.Message);
                throw ExceptionHelper.GenException(ErrorCode.E000000010);
            }

            return true;
        }

        private JObject BaseAuthParams(JObject dataset)
        {
            var query = dataset.GetJsonObject("query");
            var request = dataset.GetJsonObject("request");

            string authKey = request.GetJsonObjectStringValue("authKey");
            string apiKey = request.GetJsonObjectStringValue("apiKey");
            string requestTime = request.GetJsonObjectStringValue("requestTime");
            if (string.IsNullOrWhiteSpace(authKey))
                authKey = query.GetJsonObjectStringValue("authKey");
            if (string.IsNullOrWhiteSpace(apiKey))
                apiKey = query.GetJsonObjectStringValue("apiKey");
            if (string.IsNullOrWhiteSpace(requestTime))
                requestTime = query.GetJsonObjectStringValue("requestTime");
            if (string.IsNullOrWhiteSpace(authKey) || string.IsNullOrWhiteSpace(apiKey) || string.IsNullOrWhiteSpace(requestTime))
                return null;
            return new JObject()
            {
                { "authKey", authKey },
                { "apiKey", apiKey },
                { "requestTime", requestTime }
            };
        }

        private void Base(JObject dataset)
        {
            var param = BaseAuthParams(dataset);

            if (param == null)
                throw ExceptionHelper.GenException(ErrorCode.E000000021, "[authKey] and [apiKey] and [requestTime]");

            string authKey = param.GetJsonObjectStringValue("authKey");
            string requestTime = param.GetJsonObjectStringValue("requestTime");
            DateTime reqDateTime;
            try
            {
                reqDateTime = DateTime.ParseExact(requestTime, "yyyyMMddHHmmss", System.Globalization.CultureInfo.CurrentCulture);
            }
            catch (Exception)
            {
                throw new BaseException(ErrorCode.E000000022.ToString(), "The [requestTime] format must be yyyyMMddHHmmss");
            }
            if (reqDateTime < DateTime.UtcNow.AddMinutes(-10) || reqDateTime > DateTime.UtcNow.AddMinutes(10))
            {
                throw new BaseException(ErrorCode.E000000029.ToString(), "The [requestTime] range must be ±10 minutes of the current UTC time");
            }

            JToken sign = new GenerateAuthKey().Invoke(dataset);
            if (sign == null || string.IsNullOrWhiteSpace(sign.ToString()) || !sign.ToString().Equals(authKey, StringComparison.CurrentCultureIgnoreCase))
                throw ExceptionHelper.GenException(ErrorCode.E000000010);
        }

        private void IP(JObject dataset)
        {
            var requestUri = dataset.GetJsonObject("requesturi");
            string hostNameType = (requestUri.GetJsonObjectStringValue("HostNameType") ?? string.Empty).ToLower();
            if (hostNameType != "ipv4" && hostNameType != "ipv6")
            {
                if (BaseAuthParams(dataset) == null)
                    throw ExceptionHelper.GenException(ErrorCode.E000000010);
                Base(dataset);
            }
        }

        private void Host(JObject dataset)
        {
            var constant = dataset.GetJsonObject("constant");

            var authentication = constant.GetJsonObject("authentication");
            var hosts = authentication.GetJsonObjectValue("hosts");

            var requestUri = dataset.GetJsonObject("requesturi");
            string host = (requestUri.GetJsonObjectStringValue("Host") ?? string.Empty).ToLower();

            bool contains = false;
            JArray hostArray = hosts?.Type == JTokenType.Array ? hosts as JArray : hosts?.Type == JTokenType.String ? new JArray() { hosts } : null;
            if (hostArray != null)
            {
                foreach (var item in (JArray)hostArray)
                {
                    if (item?.ToString()?.ToLower() == host)
                    {
                        contains = true;
                        break;
                    }
                }
            }
            if (!contains)
            {
                if (BaseAuthParams(dataset) == null)
                    throw ExceptionHelper.GenException(ErrorCode.E000000010);
                Base(dataset);
            }
        }

        private void Local(JObject dataset)
        {
            var requestUri = dataset.GetJsonObject("requesturi");
            string hostNameType = (requestUri.GetJsonObjectStringValue("HostNameType") ?? string.Empty).ToLower();
            string host = (requestUri.GetJsonObjectStringValue("Host") ?? string.Empty).ToLower();
            string systemName = System.Net.Dns.GetHostName();
            if (hostNameType != "ipv4" && hostNameType != "ipv6" && host != "localhost" && host != systemName?.ToLower())
            {
                if (BaseAuthParams(dataset) == null)
                    throw ExceptionHelper.GenException(ErrorCode.E000000010);
                Base(dataset);
            }
        }
    }
}
